from ..errors import PageError

PAGE_SIZE = 8 * 1024 # 8kb
LITTLE_ORDER = 'little'

def uint8_to_byte(value):
    return int.to_bytes(value, 8, LITTLE_ORDER, signed=False)

def bytes_to_uint8(buff):
    return int.from_bytes(buff, LITTLE_ORDER, signed=False)

class BaseStructure:
    def serialize(self) -> bytes:
        # C 中的 int 为 8 bytes
        # 我们考虑到，上面header中的字段，都可以是正整数，基本上
        # 用不到负数，考虑到数据的范围，unsigned int 这个数据类型应该
        # 足够了，所以这里使用 8 bytes 的 unsigned int
        buff = bytearray()
        for field_name in self.serializable_fields():
            buff += uint8_to_byte(getattr(self, field_name))
        return bytes(buff)

    @classmethod
    def size(cls):
        # 获取类的成员的字段的长度
        return len(cls.serializable_fields()) * 8

    @classmethod
    def serializable_fields(cls):
        raise NotImplemented()

    @classmethod
    def deserialize(cls, buff: bytes):
        assert len(buff) == cls.size()
        page_header = cls()
        position = 0
        for field_name in cls.serializable_fields():
            value = bytes_to_uint8(buff[position: position+8])
            setattr(page_header, field_name, value)
            position += 8
        return page_header

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return False
        for field_name in self.serializable_fields():
            if getattr(self, field_name) != getattr(other, field_name):
                return False
        return True

    def __hash__(self):
        values = tuple(getattr(self, field_name) for field_name in self.serializable_fields())
        return hash(values)



class PageHeader(BaseStructure):
    def __init__(self):
        """
        Header 是定长数据
        """
        self.pageno = 0
        self.lsn = 0 # 来自 WAL(redo) 用于表示哪个WAL最后修改了这个页


        self.flags = 0 # 预留的标志字段
        self.reserved = 0 # 预留字段

        # slotted page 可能会用到这两个字段，用于表示
        # 最后一个record和第一个记录的位置
        self.free_space_end = 0
        self.free_space_start = 0


    @classmethod
    def serializable_fields(cls):
        return 'lsn', 'flags', 'reserved', 'free_space_start', 'free_space_end'

class RecordState:
    UNUSED = 0
    NORMARL = 1
    DEAD = 2

class Slot(BaseStructure):
    def __init__(self):
        """
        Slot 是定长数据
        """
        self.offset = 0
        self.length = 0
        self.state = 0 # 例如该state可以实现标记清除
    
    @classmethod
    def serializable_fields(cls):
        return 'offset', 'length', 'state'


class Page:
    def __init__(self, header=None):
        self.page_header = header
        if self.page_header is None:
            self.page_header = PageHeader()
        
        self.solt_directory = list() 
        self.records = bytearray()
        
    @property
    def total_record_size(self):
        return len(self.records)

    @property
    def total_slot_directory_size(self):
        return len(self.solt_directory) * Slot.size()
    
    def allocate_slot(self, record: bytes) -> Slot:
        # 我们在allocate_slot里面没有修改任何状态
        # 调用者需要自己来修改状态

        current_slot_num = len(self.solt_directory)
        
        total_slot_size = (current_slot_num + 1) * Slot.size()
        total_record_size = self.total_record_size + len(record)
        total_page_size = (self.page_header.size() + total_slot_size + total_record_size)

        if total_page_size >= PAGE_SIZE:
            # Page is full
            return None
        
        slot = Slot()
        # 这里记录逻辑位置和物理位置都可以，
        # 暂时使用物理位置，因为在Page这一层，我们还不知道 record 具体是什么对象类型，只知道他是一个 bytes
        # self.total_record_size 意思是当前 record 写入的起始位置
        slot.offset = self.total_record_size
        slot.length = len(record)
        slot.state = RecordState.UNUSED

        
        return slot

    def set_header(self, lsn):
        self.page_header.lsn = lsn
        self.page_header.free_space_start = (self.page_header.size() +
                                              self.total_slot_directory_size)
        self.page_header.free_space_end = PAGE_SIZE - len(self.records)
                                            


    def insert(self, record: bytes):
        slot = self.allocate_slot(record)
        if slot is None:
            raise PageError("out of space in the page")
        self.solt_directory.append(slot)
        self.records += record
        # 严格意义上，slotted page 的 新record是加在最前面的，即：
        # self.records = (record + self.records)
        # 但是我们可以通过total_size这个机制，实现逻辑等价
        slot.state = RecordState.NORMARL

        # 返回 slot 的下标，对于堆表来说，可以作为唯一的id
        # 即 tid (tuple id)
        return len(self.solt_directory) - 1

    def delete(self, sid) -> bool:
        if sid >= len(self.solt_directory):
            raise PageError("invalid sid.")
        slot: Slot
        slot = self.solt_directory[sid]
        # 标记为删除
        # 用到的是标记清除法，如果原地删除，对于我们Page来说，很简单，
        # 但是，有一个场景会很麻烦：索引的更新，例如
        # 我们有一个元组 tid=1，那么，其他的元组id可能是2,3,4,...
        # 如果说，直接把 tid=1 删除，那么，索引的更新就会很麻烦
        # 该元组后面的元素的 tid 也要往前挪一位，即变为 1,2,3,4,... 工作量巨大
        # 所以，我们这里使用标记清除法，标记为删除，而不是直接删除
        slot.state = RecordState.DEAD 
        return True


    def select(self, tid) -> bytes:
        if tid >= len(self.solt_directory):
            raise PageError("invalid tid.")
        slot: Slot
        slot = self.solt_directory[tid]
        # 由于我们采用了标记清除的机制
        # 所以我们要判断下该标记
        if slot.state != RecordState.NORMARL:
            return bytes() 
        record = bytes(self.records[slot.offset: slot.offset + slot.length])
        return record

    def update(self, sid, record: bytes) -> int:
        # 有两种实现方法

        # 1. 直接覆盖(虽然做到了原地更新, 但还不完善，因为，我们不知道 record 的长度是否和原来的一样, 会存在空洞)
        # 先判断是否有足够的空间进行覆盖
        slot: Slot
        slot = self.solt_directory[sid]
        if slot.length >= len(record):
            self.records[slot.offset: slot.offset + len(record)] = record
            slot.length = len(record)
            slot.state = RecordState.NORMARL
            return sid
        
        # 2. 先删除，再插入
        self.delete(sid)
        return self.insert(record)


    def serialize(self) -> bytes:
        free_space_size = (self.page_header.free_space_end - self.page_header.free_space_start)
        assert PAGE_SIZE == (self.page_header.size() +
                            self.total_slot_directory_size + 
                            free_space_size +
                            self.total_record_size
                            )
        slot_directory_bytes = bytearray()
        for slot in self.solt_directory:
            slot_directory_bytes += slot.serialize()
        return (self.page_header.serialize() + bytes(slot_directory_bytes) + bytes(free_space_size) + bytes(self.records))

    @staticmethod
    def deserialize(buff: bytes) -> "Page":
        # 反序列化头部
        header = PageHeader.deserialize(buff[:PageHeader.size()])
        page = Page(header)

        # 反序列化槽
        for slot_offset in range(header.size(), header.free_space_start, Slot.size()):
            slot = Slot.deserialize(buff[slot_offset: slot_offset + Slot.size()])
            page.solt_directory.append(slot)

        # 反序列化数据
        page.records = bytearray(buff[header.free_space_end:])
        return page
